package ru.msu.cmc.sp.oberon2;

import static org.junit.Assert.*;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.security.Permission;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class Oberon2Test {

	protected static class ExitException extends SecurityException {
		private static final long serialVersionUID = -1982617086752946683L;
		public final int status;

		public ExitException(int status) {
			super("There is no escape!");
			this.status = status;
		}
	}

	private static class NoExitSecurityManager extends SecurityManager {
		@Override
		public void checkPermission(Permission perm) {
			// allow anything.
		}

		@Override
		public void checkPermission(Permission perm, Object context) {
			// allow anything.
		}

		@Override
		public void checkExit(int status) {
			super.checkExit(status);
			throw new ExitException(status);
		}
	}

	private SecurityManager securityManager;

	@Before
	public void setUp() {
		securityManager = System.getSecurityManager();
		System.setSecurityManager(new NoExitSecurityManager());
	}

	@After
	public void tearDown() {
		System.setSecurityManager(securityManager);
	}

	private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
	private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();

	@Before
	public void setUpStreams() {
		System.setOut(new PrintStream(outContent));
		System.setErr(new PrintStream(errContent));
	}

	@After
	public void cleanUpStreams() {
		System.setOut(null);
		System.setErr(null);
	}

	@Test
	public void out1() {
		System.out.print("hello");
		assertEquals("", outContent.toString().trim());
	}

	@Test
	public void out2() {
		System.out.print("hello");
		assertEquals("hello", outContent.toString().trim());
	}

	@Test
	public void exit0() {
		try {
			System.exit(0);
		} catch (ExitException e) {
			assertEquals("Exit status", 0, e.status);
		}
	}

	@Test
	public void t0() throws Exception {
		String[] args = { "tests/t0.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 0, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("", errContent.toString().trim());
	}

	@Test
	public void t00() throws Exception {
		String[] args = { "tests/t00.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t00.ob2:0:0: NOP  : type mismatch : INT and BOOLEAN", errContent.toString().trim());
	}

	@Test
	public void t1() throws Exception {
		String[] args = { "tests/t1.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t1.ob2:3:4: Identificator x was redeclared", errContent.toString().trim());
	}

	@Test
	public void t11() throws Exception {
		String[] args = { "tests/t11.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t11.ob2:4:4: Identificator x was redeclared", errContent.toString().trim());
	}

	@Test
	public void t2() throws Exception {
		String[] args = { "tests/t2.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals(
				"tests/t2.ob2:4:6: Constant y was declared with a non-constant expression",
				errContent.toString().trim().trim());
	}

	@Test
	public void t20() throws Exception {
		String[] args = { "tests/t20.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t20.ob2:0:0: Procedure Out.String :Incompatible types of arguments : ru.msu.cmc.sp.oberon2.ArrayType@74c3aa and SHORTINT",
				errContent.toString().trim());
	}

	@Test
	public void t200() throws Exception {
		String[] args = { "tests/t200.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t200.ob2:0:0: Procedure Out.String :Incompatible types of arguments : ru.msu.cmc.sp.oberon2.ArrayType@1d9fd51 and SHORTINT",
				errContent.toString().trim());
	}

	@Test
	public void t21() throws Exception {
		String[] args = { "tests/t21.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t21.ob2:5:2:r : not a procedure", errContent.toString().trim());
	}

	@Test
	public void t22() throws Exception {
		String[] args = { "tests/t22.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 0, e.status);
		}
		assertEquals("         2", outContent.toString());
		assertEquals("", errContent.toString().trim());
	}

	@Test
	public void t221() throws Exception {
		String[] args = { "tests/t221.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t221.ob2:12:0: Procedure F :Incompatible types of arguments : argument isn't an LValue;", errContent.toString().trim());
	}

	@Test
	public void t23() throws Exception {
		String[] args = { "tests/t23.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 0, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("", errContent.toString().trim());
	}

	@Test
	public void t3() throws Exception {
		String[] args = { "tests/t3.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 0, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("", errContent.toString().trim());
	}

	@Test
	public void t31() throws Exception {
		String[] args = { "tests/t31.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t31.ob2:6:2 : x : not a procedure", errContent.toString().trim());
	}

	@Test
	public void t4() throws Exception {
		String[] args = { "tests/t4.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t4.ob2:5:0:Incompatible types in assignment INTEGER and BOOLEAN", errContent.toString().trim());
	}

	@Test
	public void t5() throws Exception {
		String[] args = { "tests/t5.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("tests/t5.ob2:7:1 Unknown varabile: z", errContent.toString().trim());
	}

	@Test
	public void t6() throws Exception {
		String[] args = { "tests/t6.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 0, e.status);
		}
		assertEquals("!23", outContent.toString().trim());
		assertEquals("", errContent.toString().trim());
	}


//	"t1.m", line 6: type CHAR is needed as argument 'c' of this procedure call
//	> Out.Char(c)
//	>          ^
//	> This expression has type INTEGER
	@Test
	public void t7() throws Exception {
		String[] args = { "tests/t7.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 100, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("", errContent.toString().trim());
	}

	@Test
	public void t8() throws Exception {
		String[] args = { "tests/t8.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 0, e.status);
		}
		assertEquals("         8", outContent.toString().trim());
		assertEquals("", errContent.toString().trim());
	}

	@Test
	public void t81() throws Exception {
		String[] args = { "tests/t81.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 0, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("", errContent.toString().trim());
	}

	@Test(timeout=1000)
	public void t82() throws Exception {
		String[] args = { "tests/t82.ob2" };
		try {
			Oberon2.main(args);
		} catch (ExitException e) {
			assertEquals("Exit status", 0, e.status);
		}
		assertEquals("", outContent.toString().trim());
		assertEquals("", errContent.toString().trim());
		assertFalse("Must be timed out", true);
	}
}
